package com.xiang.api.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
import com.wechat.pay.java.service.transferbatch.model.GetTransferBatchByOutNoRequest;
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest;
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse;
import com.wechat.pay.java.service.transferbatch.model.TransferBatchEntity;
import com.wechat.pay.java.service.transferbatch.model.TransferBatchGet;
import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput;
import com.xiang.WebSocket.WebSocketService;
import com.xiang.api.Util;
import com.xiang.api.entity.Message;
import com.xiang.sql.StaticSql;
import com.xiang.sql.entity.Order;
import jakarta.servlet.http.HttpServletRequest;

@Service
public class WxService {
  /** 商户号 */
  String merchantId;
  /** 商户API私钥路径 */
  String privateKeyPath;
  /** 商户证书序列号 */
  String merchantSerialNumber;
  /** 商户APIV3密钥 */
  String apiV3Key;
  /** 小程序appid */
  String mp_appid;

  /** 小程序search */
  String mp_search;
  /** 域名 */
  String domain;
  final String apiBaseUrl = "https://api.mch.weixin.qq.com";

  public Message login(JSONObject p) {
    String s = p.getString("code");
    if (s != null) {
      getConfig();
      p.clear();
      p.put("appid", mp_appid);
      p.put("secret", mp_search);
      p.put("js_code", s);
      p.put("grant_type", "authorization_code");
      s = Util.httpGet("https://api.weixin.qq.com/sns/jscode2session", p);
      p = JSONObject.parseObject(s);
      s = null;
      if (p.containsKey("unionid")) {
        s = p.getString("unionid");
      } else if (p.containsKey("openid")) {
        s = p.getString("openid");
      }
      if (s != null) {
        return new Message((Object) StaticSql.service.user.getTokenByWxid(s));
      }
    }
    return new Message("登录失败");
  }

  Config payConfig;
  NotificationConfig notificationConfig;

  String accessToken = "";
  long tokenTimeOut = 0;

  private String getAccessToken() {
    if (tokenTimeOut < Util.time()) {
      JSONObject p = new JSONObject();
      getConfig();
      p.put("appid", mp_appid);
      p.put("secret", mp_search);
      // p.put("grant_type", "refresh_token");
      p.put("grant_type", "client_credential");
      p = JSONObject.parse(Util.httpGet("https://api.weixin.qq.com/cgi-bin/token", p));
      if (p.containsKey("access_token") && p.containsKey("expires_in")) {
        accessToken = p.getString("access_token");
        tokenTimeOut = Util.time() + p.getLongValue("expires_in");
      } else {
        throw new RuntimeException("获取微信token错误");
      }
    }
    return accessToken;
  }

  public String getMpLink(String path) {
    if (path == null) {
      return null;
    }
    JSONObject p = new JSONObject();
    // p.put("page_url", "pages/jmp"); // 通过 Short Link
    // 进入的小程序页面路径，必须是已经发布的小程序存在的页面，可携带 query，最大1024个字符
    // return
    // Util.httpPost("https://api.weixin.qq.com/wxa/genwxashortlink?access_token=" +
    // getAccessToken(), p);
    try {
      path = java.net.URLEncoder.encode(path, "utf8");
    } catch (Exception e) {
    }

    p.put("path", "pages/jmp");
    p.put("query", "path=" + path);

    p = JSONObject
        .parse(Util.httpPost("https://api.weixin.qq.com/wxa/generate_urllink?access_token=" + getAccessToken(), p));
    return p.getString("url_link");
  }

  private Config getConfig() {
    if (Util.isEmpty(merchantId)) {
      merchantId = StaticSql.service.setting.getValueById("merchantId");
    }
    if (Util.isEmpty(privateKeyPath)) {
      privateKeyPath = "./wxcert/apiclient_key.pem";
      Util.writeFile(privateKeyPath, StaticSql.service.setting.getValueById("merchantSerialKey"));
    }
    if (Util.isEmpty(merchantSerialNumber)) {
      merchantSerialNumber = StaticSql.service.setting.getValueById("merchantSerialNumber");
    }
    if (Util.isEmpty(apiV3Key)) {
      apiV3Key = StaticSql.service.setting.getValueById("merchantApiV3Key");
    }
    if (Util.isEmpty(mp_appid)) {
      mp_appid = StaticSql.service.setting.getValueById("mpAppid");
    }
    if (Util.isEmpty(mp_search)) {
      mp_search = StaticSql.service.setting.getValueById("mpSearch");
    }
    if (Util.isEmpty(domain)) {
      domain = "https://" + StaticSql.service.setting.getValueById("domain");
    }

    if (payConfig == null) {
      payConfig = new RSAAutoCertificateConfig.Builder()
          .merchantId(merchantId)
          .privateKeyFromPath(privateKeyPath)
          .merchantSerialNumber(merchantSerialNumber)
          .apiV3Key(apiV3Key)
          .build();
    }
    return payConfig;
  }

  private NotificationConfig getNotificationConfig() {
    if (notificationConfig == null) {
      notificationConfig = new RSAAutoCertificateConfig.Builder()
          .merchantId(merchantId)
          .privateKeyFromPath(privateKeyPath)
          .merchantSerialNumber(merchantSerialNumber)
          .apiV3Key(apiV3Key)
          .build();
    }
    return notificationConfig;
  }

  @Autowired
  WebSocketService ws;

  /** 获取支付数据 */
  public JSONObject did2pd(String id, String oid) {
    getConfig();
    JSONObject result = new JSONObject();
    if (this.QueryOrder(id) == 0) {
      String timeStamp = Util.sTime(),
          nonceStr = Util.randString(32),
          s = String.format("%s\n%s\n%s\nprepay_id=%s\n", mp_appid, timeStamp, nonceStr, oid);
      result.put("id", id);
      result.put("provider", "wxpay");
      result.put("timeStamp", timeStamp);
      result.put("nonceStr", nonceStr);
      result.put("package", "prepay_id=" + oid);
      result.put("signType", "RSA");
      // result.put("total_fee", amount);
      result.put("paySign", getConfig().createSigner().sign(s).getSign());
      result.put("package", oid);
    }
    return result;
  }

  public JSONObject create(int pAmount, String title, String id, String wxid) {
    if (pAmount <= 0) {
      return null;
    }
    JsapiService service = new JsapiService.Builder().config(getConfig()).build();
    PrepayRequest request = new PrepayRequest();
    request.setAppid(mp_appid);
    request.setMchid(merchantId);
    request.setDescription(title);
    request.setOutTradeNo(id);
    request.setNotifyUrl(domain + "/notifyjsapi");
    Amount amount = new Amount();
    amount.setTotal(pAmount); // 金额
    request.setAmount(amount);
    Payer payer = new Payer();
    payer.setOpenid(wxid);
    request.setPayer(payer);
    PrepayResponse response = service.prepay(request);
    return did2pd(id, response.getPrepayId());
  }

  /** 发货 */
  public void delivery(String orderID) {
    Order o = StaticSql.service.order.getById(orderID);
    if (o != null && o.sendTime == 0) {
      String openid = StaticSql.service.user.getWxidById(o.create);
      if (Util.isEmpty(openid)) {
        return;
      }
      if (Util.isEmpty(o.name)) {
        o.name = "实体店消费线上支付";
      }
      getConfig();
      JSONObject p = new JSONObject(),
          ok = new JSONObject(),
          g = new JSONObject(),
          payer = new JSONObject();
      ok.put("order_number_type", 1);
      ok.put("mchid", merchantId);
      ok.put("out_trade_no", o.id);
      p.put("order_key", ok);
      p.put("logistics_type", 4);
      // 发货模式，发货模式枚举值：
      // 1、UNIFIED_DELIVERY（统一发货）2、SPLIT_DELIVERY（分拆发货） 示例值: UNIFIED_DELIVERY
      p.put("delivery_mode", 1);
      JSONArray list = new JSONArray();
      g.put("item_desc", o.name);
      list.add(g);
      p.put("shipping_list", list);
      p.put("upload_time", Util.timeISO8601());
      payer.put("openid", openid);
      p.put("payer", payer);
      try {
        String res = Util.httpPost(
            "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token=" + getAccessToken(), p);
        System.out.println(res);
        p = JSONObject.parse(res);
        if (Util.includes(new String[] { "0", "10060023", "10060002" }, p.getString("errcode")) < 0) {
          System.out.println("重新发货");
          StaticSql.service.task.create("发货", Util.time(), o.id);
        } else {
          System.out.println("更新发货时间" + p.toJSONString());
          StaticSql.mapper.order.update(new UpdateWrapper<Order>().eq("id", o.id).set("send_time", Util.time()));
        }
      } catch (Exception e) {
      }
    }
  }

  /** 支付回调通知 */
  public Object notifyJsapi(HttpServletRequest request) {
    // ----------------
    String wechatPaySerial = request.getHeader("Wechatpay-Serial"),
        wechatpayNonce = request.getHeader("Wechatpay-Nonce"),
        wechatSignature = request.getHeader("Wechatpay-Signature"),
        wechatTimestamp = request.getHeader("Wechatpay-Timestamp"),
        // wechatSignatureType = request.getHeader("Wechatpay-Signature-Type"),
        requestBody = Util.getRequestBody(request);
    // 构造 RequestParam
    RequestParam requestParam = new RequestParam.Builder()
        .serialNumber(wechatPaySerial)
        .nonce(wechatpayNonce)
        .signature(wechatSignature)
        .timestamp(wechatTimestamp)
        .body(requestBody)
        .build();

    // 如果已经初始化了 RSAAutoCertificateConfig，可直接使用

    // 初始化 NotificationParser
    NotificationParser parser = new NotificationParser(getNotificationConfig());
    try {
      // 以支付通知回调为例，验签、解密并转换成 Transaction
      Transaction transaction = parser.parse(requestParam, Transaction.class);
      Util.print("回调支付通知", transaction);
      StaticSql.service.order.setPayTime(transaction.getOutTradeNo(), Util.time(transaction.getSuccessTime()),
          transaction.getAmount().getPayerTotal());
    } catch (ValidationException e) {
      System.out.println("签名验证失败");
      // 签名验证失败，返回 401 UNAUTHORIZED 状态码
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED);
    }
    return ResponseEntity.status(HttpStatus.OK);
  }

  /** 关闭订单 */
  public Object closeOrder(String id) {
    JsapiService service = new JsapiService.Builder().config(getConfig()).build();

    CloseOrderRequest closeRequest = new CloseOrderRequest();
    closeRequest.setMchid(merchantId);
    closeRequest.setOutTradeNo(id);
    // 方法没有返回值，意味着成功时API返回204 No Content
    service.closeOrder(closeRequest);

    return null;
  }

  /** 查询订单，返回金额 */
  public int QueryOrder(String id) {
    JsapiService service = new JsapiService.Builder().config(getConfig()).build();
    QueryOrderByOutTradeNoRequest q = new QueryOrderByOutTradeNoRequest();
    q.setMchid(merchantId);
    q.setOutTradeNo(id);
    try {
      Transaction result = service.queryOrderByOutTradeNo(q);
      // StaticSql.service.order.setPayTime(id, Util.time(result.getSuccessTime()),
      // result.getAmount().getPayerTotal());
      return result.getAmount().getPayerTotal();
    } catch (RuntimeException e) {
    }
    return 0;
  }

  /** 格式化转账状态 */
  public String formatTransferStatus(String input) {
    switch (input) {
      case "ACCEPTED":
        input = "已受理";
        break;
      case "PROCESSING":
        input = "转账中";
        break;
      case "FINISHED":
        input = "已完成";
        break;
      case "CLOSED":
        input = "已关闭";
        break;

      default:
        break;
    }
    return input;
  }

  /** 开始转账 */
  public String transfer(String outBatchNo, String openid, long money, String title, String remark) {
    if (Util.isEmpty(outBatchNo) || Util.isEmpty(openid) || money <= 0 || Util.isEmpty(title) || Util.isEmpty(remark)) {
      return null;
    }
    if (title.length() > 32) {
      title = title.substring(0, 29) + "...";
    }
    TransferBatchService service = new TransferBatchService.Builder().config(getConfig()).build();
    InitiateBatchTransferRequest req = new InitiateBatchTransferRequest();
    req.setAppid(mp_appid);
    req.setOutBatchNo(outBatchNo);
    req.setBatchName(title); // 名称
    req.setBatchRemark(remark); // 批次备注
    req.setTotalAmount(money);
    req.setTotalNum(1);
    // req.setTransferSceneId("1000"); //
    // 设置转账场景码，如不设置，则为默认场景
    List<TransferDetailInput> detail = new ArrayList<>();
    TransferDetailInput t = new TransferDetailInput();
    t.setTransferAmount(money);
    t.setOutDetailNo(String.format("%sN%d", outBatchNo, 0));
    t.setOpenid(openid);
    t.setTransferRemark(String.format("%sN%d", outBatchNo, 0)); // 备注
    detail.add(t);
    req.setTransferDetailList(detail);
    // 发起商家转账
    InitiateBatchTransferResponse res;
    try {
      res = service.initiateBatchTransfer(req);
    } catch (ServiceException e) {
      System.out.println("转账错误:" + remark);
      e.printStackTrace();
      return e.getErrorMessage();
    }
    // res.getBatchStatus().equals("ACCEPTED") // 获取状态码
    return formatTransferStatus(res.getBatchStatus());
  }

  public TransferBatchGet QueryTransfer(String id) {
    // com.wechat.pay.java.service.transferbatch.model
    TransferBatchService service = new TransferBatchService.Builder().config(getConfig()).build();
    GetTransferBatchByOutNoRequest req = new GetTransferBatchByOutNoRequest();
    // GetTransferBatchByOutNoRequest
    req.setOutBatchNo(id);
    req.setNeedQueryDetail(false);
    TransferBatchEntity res;
    try {
      res = service.getTransferBatchByOutNo(req);
    } catch (ServiceException e) {
      throw new RuntimeException(e.getErrorMessage());
    }
    return res.getTransferBatch();
  }

  public String QueryTransferStatus(String id) {
    return formatTransferStatus(QueryTransfer(id).getBatchStatus());
  }
}
